/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import Long from '../util/long/index'

export default class Unsafe {
    static ARRAY_BYTE_BASE_OFFSET: number = 16

    static putLong(values: Array<Long> | Uint8Array | Int8Array | Int16Array | Int32Array, offset: number,
                   x: Long): void {
        Unsafe.putData(values, offset, x, 16);
    }

    static putInt(values: Array<Long> | Uint8Array | Int8Array | Int16Array | Int32Array, offset: number,
                  x: number): void {
        Unsafe.putData(values, offset, x, 8);
    }

    static putShort(values: Array<Long> | Uint8Array | Int8Array | Int16Array | Int32Array, offset: number,
                    x: number): void {
        Unsafe.putData(values, offset, x, 4);
    }

    static putByte(values: Array<Long> | Uint8Array | Int8Array | Int16Array | Int32Array, offset: number,
                   x: number): void {
        Unsafe.putData(values, offset, x, 2);
    }

    static getLong(values: Array<Long> | Uint8Array | Int8Array | Int16Array | Int32Array, offset: number): Long {
        return Unsafe.getData(values, offset, 16);
    }

    static getInt(values: Array<Long> | Uint8Array | Int8Array | Int16Array | Int32Array, offset: number): number {
        return Unsafe.getData(values, offset, 8);
    }

    static getShort(values: Array<Long> | Uint8Array | Int8Array | Int16Array | Int32Array, offset: number): number {
        return Unsafe.getData(values, offset, 4);
    }

    static getByte(values: Array<Long> | Uint8Array | Int8Array | Int16Array | Int32Array, offset: number): number {
        return Unsafe.getData(values, offset, 2);
    }

    private static putData(object: Array<Long> | Uint8Array | Int8Array | Int16Array | Int32Array, offset: number,
                           x: Long | number, xBitPosition: number): void {
        let valueData: string = Unsafe.getDataHex16Length(x, xBitPosition);
        let arrayValueString: string = "";
        let arrayOffset: number = 16;
        let longArrayScale: number = 8;
        let arrayScale: number = 1;
        let values: any;
        if (Unsafe.isArrayLong(object)) {
            values = object as Array<Long>;
            arrayScale = 8;
        } else if (object instanceof Int32Array) {
            values = object as Int32Array;
            arrayScale = 4;
        } else if (object instanceof Int16Array) {
            values = object as Int16Array;
            arrayScale = 2;
        } else if (object instanceof Int8Array) {
            values = object as Int8Array;
            arrayScale = 1;
        } else if (object instanceof Uint8Array) {
            values = object as Uint8Array;
            arrayScale = 1;
        }
        let scaleIndex: number = offset - arrayOffset;
        let divisor: number = Math.floor(scaleIndex / arrayScale);
        let rem: number = scaleIndex % arrayScale;
        if (values.length > divisor) {
            for (let i = 0; i < ((longArrayScale / arrayScale) * 2); i++) {
                if (values.length > (divisor + i) && typeof (values[divisor + i]) != "undefined") {
                    arrayValueString = Unsafe.getDataHex16Length(values[divisor + i], arrayScale * 2)
                    + arrayValueString;
                } else {
                    arrayValueString = Unsafe.getDataHex16Length(0, arrayScale * 2)
                    + arrayValueString;
                }
            }
            arrayValueString = Unsafe.replacePos(arrayValueString, arrayValueString.length - rem * 2 - xBitPosition,
                arrayValueString.length - rem * 2, valueData);
            let length: number = arrayValueString.length;
            for (let i = 0; i < ((longArrayScale / arrayScale) * 2); i++) {
                if (values.length > (divisor + i)) {
                    if (Unsafe.isArrayLong(object)) {
                        values[divisor + i] = Long.fromString(arrayValueString.substring(length - ((i + 1) * arrayScale * 2),
                            length - (i * arrayScale * 2)), 16);
                    } else if (object instanceof Int32Array) {
                        values[divisor + i] = parseInt(arrayValueString.substring(length - ((i + 1) * arrayScale * 2),
                            length - (i * arrayScale * 2)), 16) & 0xFFFFFFFF;
                    } else if (object instanceof Int16Array) {
                        values[divisor + i] = parseInt(arrayValueString.substring(length - ((i + 1) * arrayScale * 2),
                            length - (i * arrayScale * 2)), 16) & 0xFFFF;
                    } else if (object instanceof Int8Array) {
                        values[divisor + i] = parseInt(arrayValueString.substring(length - ((i + 1) * arrayScale * 2),
                            length - (i * arrayScale * 2)), 16) & 0xFF;
                    } else if (object instanceof Uint8Array) {
                        values[divisor + i] = parseInt(arrayValueString.substring(length - ((i + 1) * arrayScale * 2),
                            length - (i * arrayScale * 2)), 16) & 0xFF;
                    }
                }
            }
        }
    }

    private static getData(object: Array<Long> | Uint8Array | Int8Array | Int16Array | Int32Array, offset: number,
                           xBitPosition: number): any {
        let arrayValueString: string = "";
        let arrayOffset: number = 16;
        let arrayScale: number = 8;
        let longArrayScale: number = 8;
        let values: any;
        if (Unsafe.isArrayLong(object)) {
            values = object as Array<Long>;
            arrayScale = 8;
        } else if (object instanceof Int32Array) {
            values = object as Int32Array;
            arrayScale = 4;
        } else if (object instanceof Int16Array) {
            values = object as Int16Array;
            arrayScale = 2;
        } else if (object instanceof Int8Array) {
            values = object as Int8Array;
            arrayScale = 1;
        } else if (object instanceof Uint8Array) {
            values = object as Uint8Array;
            arrayScale = 1;
        }
        let scaleIndex: number = offset - arrayOffset;
        let divisor: number = Math.floor(scaleIndex / arrayScale);
        let rem: number = scaleIndex % arrayScale;
        if (values.length > divisor) {
            for (let i = 0; i < ((longArrayScale / arrayScale) * 2); i++) {
                if (values.length > (divisor + i) && typeof (values[divisor + i]) != "undefined") {
                    arrayValueString = Unsafe.getDataHex16Length(values[divisor + i], arrayScale * 2)
                    + arrayValueString;
                } else {
                    arrayValueString = Unsafe.getDataHex16Length(0, arrayScale * 2)
                    + arrayValueString;
                }
            }
            let length = arrayValueString.length;
            let data: string = arrayValueString.substring(length - 2 * rem - xBitPosition, length - 2 * rem);
            if (xBitPosition == 16) {
                return Long.fromString(data, 16);
            } else if (xBitPosition == 8) {
                return new Int32Array([parseInt(data, 16) & 0xFFFFFFFF])[0];
            } else if (xBitPosition == 4) {
                return new Int16Array([parseInt(data, 16) & 0xFFFF])[0];
            } else if (xBitPosition == 2) {
                return new Int8Array([parseInt(data, 16) & 0xFF])[0];
            }
        }
        return 0;
    }

    private static getDataHex16Length(data: any, bitPosition: number): string {
        let value: string = "";
        let isZero = false;
        if (data instanceof Long) {
            if (data.isZero()) {
                isZero = true;
            } else {
                isZero = false;
            }
        } else {
            if (data > 0 || data < 0) {
                isZero = false;
            } else {
                isZero = true;
            }
        }
        if (isZero) {
            for (let i = 0; i < bitPosition; i++) {
                value = "0" + value;
            }
        } else {
            let dataString: string = "";
            if (bitPosition == 16) {
                dataString = data.toUnsigned().toString(16);
            } else if (bitPosition == 8) {
                // & 0xFFFFFFFF保留到32位int类型, >>>转成无符号整数
                dataString = ((data & 0xFFFFFFFF) >>> 0).toString(16);
            } else if (bitPosition == 4) {
                // & 0xFFFF保留到16位short类型, >>>转成无符号整数
                dataString = ((data & 0xFFFF) >>> 0).toString(16);
            } else if (bitPosition == 2) {
                // & 0xFFFF保留到8位byte类型, >>>转成无符号整数
                dataString = ((data & 0xFF) >>> 0).toString(16);
            }
            for (let i = 0; i < (bitPosition - dataString.length); i++) {
                value = "0" + value;
            }
            value = value + dataString;
        }
        return value;
    }

    private static replacePos(text: string, start: number, stop: number, replaceText: string): string {
        let myStr: string = text.substring(0, start) + replaceText + text.substring(stop);
        return myStr;
    }

    private static isArrayLong(object: any): boolean {
        if (object instanceof Array) {
            return true;
        } else {
            return false;
        }
    }

    static copyMemory(srcBase: Array<Long> | Uint8Array | Int8Array | Int16Array | Int32Array, srcOffset: number,
                      destBase: Array<Long> | Uint8Array | Int8Array | Int16Array | Int32Array, destOffset: number,
                      bytes: Long): void {
        let valueData: string = Unsafe.getOffsetArrayString(srcBase, srcOffset, bytes);
        let arrayValueString: string = "";
        let arrayOffset: number = 16;
        let arrayScale: number = 1;
        let values: any;
        if (Unsafe.isArrayLong(destBase)) {
            values = destBase as Array<Long>;
            arrayScale = 8;
        } else if (destBase instanceof Int32Array) {
            values = destBase as Int32Array;
            arrayScale = 4;
        } else if (destBase instanceof Int16Array) {
            values = destBase as Int16Array;
            arrayScale = 2;
        } else if (destBase instanceof Int8Array) {
            values = destBase as Int8Array;
            arrayScale = 1;
        } else if (destBase instanceof Uint8Array) {
            values = destBase as Uint8Array;
            arrayScale = 1;
        }
        let scaleIndex: number = destOffset - arrayOffset;
        let divisor: number = Math.floor(scaleIndex / arrayScale);
        let rem: number = scaleIndex % arrayScale;
        if (values.length > divisor) {
            let indexPosition: number = 0;
            for (let i = 0; i < (valueData.length / (arrayScale * 2)); i++) {
                if (values.length > (divisor + i)) {
                    if (typeof (values[divisor + i]) != "undefined") {
                        arrayValueString = Unsafe.getDataHex16Length(values[divisor + i], arrayScale * 2)
                        + arrayValueString;
                    } else {
                        arrayValueString = Unsafe.getDataHex16Length(0, arrayScale * 2)
                        + arrayValueString;
                    }
                    if ((i + 1) >= (valueData.length / (arrayScale * 2))) {
                        indexPosition = i;
                    }
                } else {
                    indexPosition = i - 1;
                    break
                }
            }
            if ((arrayValueString.length - rem * 2) >= valueData.length) {
                arrayValueString = Unsafe.replacePos(arrayValueString, arrayValueString.length - rem * 2 - valueData.length,
                    arrayValueString.length - rem * 2, valueData);
            } else {
                arrayValueString = valueData.substring(valueData.length - (arrayValueString.length - rem * 2))
                + arrayValueString.substring(arrayValueString.length - rem * 2);
            }
            let length = arrayValueString.length;
            for (let i = 0; i <= indexPosition; i++) {
                if (Unsafe.isArrayLong(destBase)) {
                    values[divisor + i] = Long.fromString(arrayValueString.substring(length - ((i + 1) * arrayScale * 2),
                        length - (i * arrayScale * 2)), 16);
                } else if (destBase instanceof Int32Array) {
                    values[divisor + i] = parseInt(arrayValueString.substring(length - ((i + 1) * arrayScale * 2),
                        length - (i * arrayScale * 2)), 16) & 0xFFFFFFFF;
                } else if (destBase instanceof Int16Array) {
                    values[divisor + i] = parseInt(arrayValueString.substring(length - ((i + 1) * arrayScale * 2),
                        length - (i * arrayScale * 2)), 16) & 0xFFFF;
                } else if (destBase instanceof Int8Array) {
                    values[divisor + i] = parseInt(arrayValueString.substring(length - ((i + 1) * arrayScale * 2),
                        length - (i * arrayScale * 2)), 16) & 0xFF;
                } else if (destBase instanceof Uint8Array) {
                    values[divisor + i] = parseInt(arrayValueString.substring(length - ((i + 1) * arrayScale * 2),
                        length - (i * arrayScale * 2)), 16) & 0xFF;
                }
            }
        }
    }

    private static getOffsetArrayString(srcBase: Array<Long> | Uint8Array | Int8Array | Int16Array | Int32Array,
                                        srcOffset: number, bytes: Long): string {
        let arrayValueString: string = "";
        let arrayOffset: number = 16;
        let arrayScale: number = 8;
        let values: any;
        if (Unsafe.isArrayLong(srcBase)) {
            values = srcBase as Array<Long>;
            arrayScale = 8;
        } else if (srcBase instanceof Int32Array) {
            values = srcBase as Int32Array;
            arrayScale = 4;
        } else if (srcBase instanceof Int16Array) {
            values = srcBase as Int16Array;
            arrayScale = 2;
        } else if (srcBase instanceof Int8Array) {
            values = srcBase as Int8Array;
            arrayScale = 1;
        } else if (srcBase instanceof Uint8Array) {
            values = srcBase as Uint8Array;
            arrayScale = 1;
        }
        let scaleIndex: number = srcOffset - arrayOffset;
        let divisor: number = Math.floor(scaleIndex / arrayScale);
        let rem: number = scaleIndex % arrayScale;
        if (values.length > divisor) {
            for (let i = 0; Long.fromNumber(i).lessThan(bytes.divide(arrayScale)); i++) {
                if (values.length > (divisor + i)) {
                    if (typeof (values[divisor + i]) != "undefined") {
                        arrayValueString = Unsafe.getDataHex16Length(values[divisor + i], arrayScale * 2)
                        + arrayValueString;
                    } else {
                        arrayValueString = Unsafe.getDataHex16Length(0, arrayScale * 2)
                        + arrayValueString;
                    }
                } else {
                    break
                }
            }
            let length = arrayValueString.length;
            let data;
            if (bytes.multiply(2).lessThan(length - 2 * rem)) {
                data = arrayValueString.substring(Long.fromNumber(length - 2 * rem).sub(bytes.multiply(2)).toNumber(),
                    length - 2 * rem);
            } else {
                data = arrayValueString.substring(0, length - 2 * rem);
            }
            return data;
        }
        return "";
    }
}